﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Go;
using GoRpc;

namespace LoadFile
{
    public partial class MainForm : Form
    {
        class NodeTag
        {
            public bool isFile;
            public string name;
            public generator.child action;
        }

        control_strand strand;
        generator _action;
        chan<tuple<TreeNode, bool>> expandChan;
        chan<tuple<string, string>> fileChan;
        chan<tuple<string, string>> dirChan;
        chan<tuple<TreeNode, string[]>> dragChan;

        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            textBox_ip.Focus();
            strand = new control_strand(this);
            expandChan = chan<tuple<TreeNode, bool>>.make(strand, -1);
            fileChan = chan<tuple<string, string>>.make(strand, -1);
            dirChan = chan<tuple<string, string>>.make(strand, -1);
            dragChan = chan<tuple<TreeNode, string[]>>.make(strand, -1);
            generator.go(out _action, strand, () => Action(null), delegate ()
            {
                treeView1.Nodes.Clear();
                Reset();
            });
        }

        private void button_ip_Click(object sender, EventArgs e)
        {
            _action.stop();
        }

        private void Reset()
        {
            generator.go(out _action, strand, () => Action(textBox_ip.Text), delegate ()
            {
                treeView1.Nodes.Clear();
                Reset();
            });
        }

        static void GetFullPath(StringBuilder builder, TreeNode node)
        {
            NodeTag tag = (NodeTag)node.Tag;
            if (node.Parent != null)
            {
                GetFullPath(builder, node.Parent);
                builder.Append('\\');
            }
            builder.Append(tag.name);
        }

        StringBuilder builder = new StringBuilder(256);
        static string[] sizeFmt = new string[] { "0 B", "0.00 KB", "0.00 MB", "0.00 GB", "0.00 TB" };
        private async Task EnumTree(Rpc.Session session, TreeNode tree)
        {
            builder.Clear();
            GetFullPath(builder, tree);
            string path = builder.ToString();
            var dirs = await session.Call<string[]>("GetDirectories", path);
            if (dirs.state == Rpc.ReplyState.Success)
            {
                for (int j = 0; j < dirs.result.Length; j++)
                {
                    TreeNode newNode = tree.Nodes.Add(dirs.result[j]);
                    newNode.Tag = new NodeTag { isFile = false, name = dirs.result[j] };
                    newNode.Nodes.Add($".\\").Tag = new NodeTag { isFile = false };
                    await generator.sleep(1);
                }
            }
            var files = await session.Call<string[]>("GetFiles", path);
            if (files.state == Rpc.ReplyState.Success)
            {
                for (int j = 0; j < files.result.Length; j++)
                {
                    var size = await session.Call<long>("FileSize", $"{path}\\{files.result[j]}");
                    string str = null;
                    for (int k = sizeFmt.Length - 1; k >= 0; k--)
                    {
                        if (size.result >= (1L << (k * 10)))
                        {
                            str = ((double)size.result / (1L << (k * 10))).ToString(sizeFmt[k]);
                            break;
                        }
                    }
                    TreeNode newNode = tree.Nodes.Add($"{files.result[j]}  {str ?? "0 B"}");
                    newNode.Tag = new NodeTag { isFile = true, name = files.result[j] };
                }
            }
        }

        private async Task StopEnumAction(generator.children enumChild, TreeNode tree)
        {
            if (null != tree.Tag)
            {
                NodeTag tag = (NodeTag)tree.Tag;
                if (null != tag.action)
                {
                    await enumChild.stop(tag.action);
                    tag.action = null;
                }
                foreach (TreeNode item in tree.Nodes)
                {
                    await StopEnumAction(enumChild, item);
                }
            }
        }

        private async Task<bool> ReadFile(Rpc.Session session, string srcFile, string dstFile, chan<float> progress)
        {
            string callback = session.OverCallback(async delegate (object[] args)
            {
                MemoryMappedFile mmf = null;
                MemoryMappedViewAccessor mmv = null;
                try
                {
                    Rpc.OverRecv[] recvs = Rpc.Session.OverRecv();
                    if (null != recvs)
                    {
                        generator.lock_stop();
                        await generator.send_task(delegate ()
                        {
                            mmf = MemoryMappedFile.CreateFromFile(dstFile, FileMode.Create, null, recvs[0].Length);
                            mmv = mmf.CreateViewAccessor();
                        });
                        generator.unlock_stop();
                        progress.post(0f);
                        while (!recvs[0].Full && !recvs[0].Cancel)
                        {
                            await session.NamedRecv(recvs[0], socket.get_mmview_ptr(mmv), recvs[0].Offset, (int)Math.Min(1024 * 1024, recvs[0].Length - recvs[0].Offset));
                            progress.post((float)recvs[0].Offset / recvs[0].Length);
                        }
                        return recvs[0].Full;
                    }
                    else
                    {
                        await generator.send_task(() => File.Create(dstFile).Dispose());
                        return true;
                    }
                }
                finally
                {
                    generator.lock_stop();
                    await generator.send_task(delegate ()
                    {
                        mmv?.Dispose();
                        mmf?.Dispose();
                        try
                        {
                            DateTime[] info = (DateTime[])args[0];
                            File.SetCreationTime(dstFile, info[0]);
                            File.SetLastWriteTime(dstFile, info[1]);
                            File.SetLastAccessTime(dstFile, info[2]);
                        }
                        catch (Exception) { }
                    });
                    generator.unlock_stop();
                }
            });
            Rpc.Result<bool> result = await session.Call<bool>("ReadFile", srcFile, callback);
            if (result.state == Rpc.ReplyState.Success)
            {
                return result.result;
            }
            return false;
        }

        private async Task<bool> ReadFileXP(Rpc.Session session, string srcFile, string dstFile, chan<float> progress)
        {
            string callback = session.OverCallback(async delegate (object[] args)
            {
                Rpc.OverRecv[] recvs = Rpc.Session.OverRecv();
                FileStream file = null;
                try
                {
                    generator.lock_stop();
                    await generator.send_task(delegate ()
                    {
                        file = File.Open(dstFile, FileMode.Create);
                    });
                    generator.unlock_stop();
                    if (null != recvs)
                    {
                        progress.post(0f);
                        Rpc.RecvDoubleBuffer dbuff = new Rpc.RecvDoubleBuffer(1024 * 1024);
                        long offset = 0;
                        for (offset = 0; offset < recvs[0].Length;)
                        {
                            int len = await Rpc.Session.Self.NamedRecv(recvs[0], dbuff.RecvBuff, 0, dbuff.RecvBuff.Length);
                            if (len <= 0)
                            {
                                break;
                            }
                            await dbuff.Exchange();
                            if (dbuff.Error)
                            {
                                break;
                            }
                            dbuff.Async(() => file.Write(dbuff.WriteData, 0, len));
                            offset += len;
                            progress.post((float)offset / recvs[0].Length);
                        }
                        await dbuff.Exchange();
                        progress.post((float)offset / recvs[0].Length);
                        return true;
                    }
                    else
                    {
                        return true;
                    }
                }
                finally
                {
                    generator.lock_stop();
                    await generator.send_task(delegate ()
                    {
                        try
                        {
                            file?.Dispose();
                            DateTime[] info = (DateTime[])args[0];
                            File.SetCreationTime(dstFile, info[0]);
                            File.SetLastWriteTime(dstFile, info[1]);
                            File.SetLastAccessTime(dstFile, info[2]);
                        }
                        catch (Exception) { }
                    });
                    generator.unlock_stop();
                }
            });
            Rpc.Result<bool> result = await session.Call<bool>("ReadFileXP", srcFile, callback);
            if (result.state == Rpc.ReplyState.Success)
            {
                return result.result;
            }
            return false;
        }

        private async Task<bool> WriteFile(Rpc.Session session, string srcFile, string dstFile, chan<float> progress)
        {
            FileStream file = null;
            DateTime[] info = null;
            try
            {
                generator.lock_stop();
                await generator.send_task(delegate ()
                {
                    try
                    {
                        file = File.OpenRead(srcFile);
                        info = new DateTime[]
                        {
                            File.GetCreationTime(srcFile),
                            File.GetLastWriteTime(srcFile),
                            File.GetLastAccessTime(srcFile),
                        };
                    }
                    catch (Exception) { }
                });
                generator.unlock_stop();
                if (null != file)
                {
                    if (file.Length > 0)
                    {
                        progress.post(0f);
                        Rpc.SendDoubleBuffer dbuff = new Rpc.SendDoubleBuffer(1024 * 1024);
                        dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, file.Length)));
                        session.OverNamedSend(file.Length, async delegate (Rpc.SendName name, long offset, long count)
                        {
                            await dbuff.Exchange();
                            if (dbuff.Error)
                            {
                                return;
                            }
                            var data = dbuff.SendData;
                            if (count - data.Count > 0)
                            {
                                dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, count - data.Count)));
                            }
                            await session.NamedSend(name, data);
                            progress.post((float)(offset + data.Count) / file.Length);
                        });
                    }
                    else
                    {
                        progress.post(1f);
                    }
                    var res = await session.Call("WriteFile", dstFile, info);
                    if (res.state != Rpc.ReplyState.Success)
                    {
                        return false;
                    }
                    return true;
                }
                return false;
            }
            catch (generator.stop_exception)
            {
                throw;
            }
            finally
            {
                file?.Dispose();
            }
        }

        private async Task Action(string addr)
        {
            string ip = null;
            int port = 0;
            await generator.send_task(delegate ()
            {
                try
                {
                    if (null == addr)
                    {
                        addr = File.ReadAllText("./Address.txt");
                    }
                    string[] addr_ = addr.Split(':');
                    port = int.Parse(addr_[1]);
                    ip = addr_[0];
                }
                catch (Exception) { }
            });
            if (null == ip)
            {
                await generator.hold();
                return;
            }
            textBox_ip.Text = addr;
            while (true)
            {
                socket_tcp socket = new socket_tcp();
                Rpc.Methods methods = new Rpc.Methods();
                Rpc.Session session = new Rpc.Session(strand, socket, methods);
                try
                {
                    if (await socket.connect(ip, port))
                    {
                        await generator.send_task(() => File.WriteAllText("./Address.txt", addr));
                        expandChan.post(default);
                        generator.children children = new generator.children();
                        chan<float> progressChan = chan<float>.make(-1);
                        children.go(async delegate ()
                        {
                            progressBar1.Value = 0;
                            while (true)
                            {
                                float progress = await progressChan.receive();
                                progressBar1.Value = (int)(progressBar1.Maximum * progress);
                            }
                        });
                        children.go(async delegate ()
                        {
                            generator.children enumChild = new generator.children();
                            while (true)
                            {
                                tuple<TreeNode, bool> tree = await expandChan.receive();
                                if (null == tree.value1)
                                {
                                    await enumChild.stop();
                                    treeView1.Nodes.Clear();
                                    enumChild.free_go(async delegate ()
                                    {
                                        var drives = await session.Call<string[]>("GetDrives");
                                        if (drives.state == Rpc.ReplyState.Success)
                                        {
                                            for (int i = 0; i < drives.result.Length; i++)
                                            {
                                                string name = drives.result[i];
                                                TreeNode newNode = treeView1.Nodes.Add(name);
                                                newNode.Tag = new NodeTag { isFile = false, name = name };
                                                newNode.Nodes.Add($".\\").Tag = new NodeTag { isFile = false };
                                                await generator.sleep(1);
                                            }
                                        }
                                    });
                                }
                                else
                                {
                                    await StopEnumAction(enumChild, tree.value1);
                                    tree.value1.Nodes.Clear();
                                    if (tree.value2)
                                    {
                                        NodeTag tag = (NodeTag)tree.value1.Tag;
                                        enumChild.free_go(out tag.action, async delegate ()
                                        {
                                            await EnumTree(session, tree.value1);
                                        });
                                    }
                                    else
                                    {
                                        tree.value1.Nodes.Add($".\\");
                                    }
                                }
                            }
                        });
                        children.go(async delegate ()
                        {
                            generator.children writeChild = new generator.children();
                            while (true)
                            {
                                tuple<string, string> tree = await fileChan.receive();
                                if (null != tree.value1)
                                {
                                    string file = tree.value1;
                                    writeChild.go(async delegate ()
                                    {
                                        try
                                        {
                                            bool ok;
                                            if (checkBox_xp.Checked)
                                            {
                                                ok = await ReadFileXP(session, file, tree.value2, progressChan);
                                            }
                                            else
                                            {
                                                ok = await ReadFile(session, file, tree.value2, progressChan);
                                            }
                                            if (!ok)
                                            {
                                                textBox1.AppendText("~ ");
                                            }
                                            textBox1.AppendText($"--> {tree.value2}");
                                            textBox1.AppendText("\r\n");
                                        }
                                        finally
                                        {
                                            button_Load.Text = "传输";
                                        }
                                    });
                                }
                                else
                                {
                                    await writeChild.stop();
                                }
                            }
                        });
                        children.go(async delegate ()
                        {
                            generator.children writeChild = new generator.children();
                            while (true)
                            {
                                tuple<string, string> tree = await dirChan.receive();
                                if (null != tree.value1)
                                {
                                    Func<string, string, Task> copyAction = null;
                                    copyAction = async delegate (string srcDir, string dstDir)
                                    {
                                        await generator.send_task(() => Directory.CreateDirectory(dstDir));
                                        var files = await session.Call<string[]>("GetFiles", srcDir);
                                        if (files.state == Rpc.ReplyState.Success)
                                        {
                                            for (int j = 0; j < files.result.Length; j++)
                                            {
                                                bool ok;
                                                if (checkBox_xp.Checked)
                                                {
                                                    ok = await ReadFileXP(session, $"{srcDir}\\{files.result[j]}", $"{dstDir}\\{files.result[j]}", progressChan);
                                                }
                                                else
                                                {
                                                    ok = await ReadFile(session, $"{srcDir}\\{files.result[j]}", $"{dstDir}\\{files.result[j]}", progressChan);
                                                }
                                                if (!ok)
                                                {
                                                    textBox1.AppendText("~ ");
                                                }
                                                textBox1.AppendText($"--> {dstDir}\\{files.result[j]}");
                                                textBox1.AppendText("\r\n");
                                            }
                                        }
                                        var dirs = await session.Call<string[]>("GetDirectories", srcDir);
                                        if (dirs.state == Rpc.ReplyState.Success)
                                        {
                                            for (int j = 0; j < dirs.result.Length; j++)
                                            {
                                                textBox1.AppendText($"*--> {dstDir}\\{dirs.result[j]}");
                                                textBox1.AppendText("\r\n");
                                                await copyAction($"{srcDir}\\{dirs.result[j]}", $"{dstDir}\\{dirs.result[j]}");
                                            }
                                        }
                                        var info = await session.Call<DateTime[]>("DirectoryInfo", srcDir);
                                        if (info.state == Rpc.ReplyState.Success)
                                        {
                                            await generator.send_task(delegate ()
                                            {
                                                try
                                                {
                                                    Directory.SetCreationTime(dstDir, info.result[0]);
                                                    Directory.SetLastWriteTime(dstDir, info.result[1]);
                                                    Directory.SetLastAccessTime(dstDir, info.result[2]);
                                                }
                                                catch (Exception) { }
                                            });
                                        }
                                    };
                                    writeChild.go(async delegate ()
                                    {
                                        try
                                        {
                                            await copyAction(tree.value1, tree.value2);
                                        }
                                        finally
                                        {
                                            button_Load.Text = "传输";
                                        }
                                    });
                                }
                                else
                                {
                                    await writeChild.stop();
                                }
                            }
                        });
                        children.go(async delegate ()
                        {
                            generator.children writeChild = new generator.children();
                            while (true)
                            {
                                tuple<TreeNode, string[]> copys = await dragChan.receive();
                                if (null != copys.value1)
                                {
                                    Func<TreeNode, string[], string, Task> copyAction = null;
                                    copyAction = async delegate (TreeNode root, string[] srcDir, string dstDir)
                                    {
                                        if (root.IsExpanded)
                                        {
                                            expandChan.post(tuple.make(root, true));
                                        }
                                        for (int i = 0; i < srcDir.Length; i++)
                                        {
                                            string name = Path.GetFileName(srcDir[i]);
                                            if (File.Exists(srcDir[i]))
                                            {
                                                bool ok = await WriteFile(session, srcDir[i], $"{dstDir}\\{name}", progressChan);
                                                if (!ok)
                                                {
                                                    textBox1.AppendText("~  ");
                                                }
                                                textBox1.AppendText($"--> {srcDir[i]}");
                                                textBox1.AppendText("\r\n");
                                                if (root.IsExpanded)
                                                {
                                                    expandChan.post(tuple.make(root, true));
                                                }
                                            }
                                            else if (Directory.Exists(srcDir[i]))
                                            {
                                                textBox1.AppendText($"*--> {srcDir[i]}");
                                                textBox1.AppendText("\r\n");
                                                await session.Call("CreateDirectory", $"{dstDir}\\{name}");
                                                TreeNode newNode = root.Nodes.Add(name);
                                                newNode.Tag = new NodeTag { isFile = false, name = name };
                                                string[] files = await generator.send_task(() => Directory.GetFiles(srcDir[i]));
                                                await copyAction(newNode, files, $"{dstDir}\\{name}");
                                                string[] dirs = await generator.send_task(() => Directory.GetDirectories(srcDir[i]));
                                                await copyAction(newNode, dirs, $"{dstDir}\\{name}");
                                                if (root.IsExpanded)
                                                {
                                                    expandChan.post(tuple.make(root, true));
                                                }
                                            }
                                        }
                                    };
                                    writeChild.go(async delegate ()
                                    {
                                        try
                                        {
                                            builder.Clear();
                                            GetFullPath(builder, copys.value1);
                                            string dstDir = builder.ToString();
                                            await copyAction(copys.value1, copys.value2, dstDir);
                                        }
                                        finally
                                        {
                                            button_Load.Text = "传输";
                                        }
                                    });
                                }
                                else
                                {
                                    await writeChild.stop();
                                }
                            }
                        });
                        session.Start();
                        await session.Wait();
                        await children.stop();
                        treeView1.Nodes.Clear();
                    }
                }
                finally
                {
                    session.Close();
                    socket.close();
                }
                await generator.sleep(1000);
            }
        }

        private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            expandChan.post(tuple.make(e.Node, true));
        }

        private void treeView1_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
        {
            expandChan.post(tuple.make(e.Node, false));
        }

        private void button_Load_Click(object sender, EventArgs e)
        {
            if (null == treeView1.SelectedNode)
            {
                return;
            }
            if (button_Load.Text == "传输")
            {
                StringBuilder builder = new StringBuilder(256);
                GetFullPath(builder, treeView1.SelectedNode);
                string srcFile = builder.ToString();
                string dstFile = null;
                if (((NodeTag)treeView1.SelectedNode.Tag).isFile)
                {
                    SaveFileDialog saveFileDialog = new SaveFileDialog();
                    saveFileDialog.FileName = Path.GetFileName(srcFile);
                    if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
                    {
                        dstFile = saveFileDialog.FileName;
                    }
                }
                else
                {
                    FolderBrowserDialog saveFileDialog = new FolderBrowserDialog();
                    saveFileDialog.SelectedPath = Path.GetFileName(srcFile);
                    if (saveFileDialog.ShowDialog(this) == DialogResult.OK)
                    {
                        dstFile = saveFileDialog.SelectedPath;
                    }
                }
                if (null != dstFile)
                {
                    button_Load.Text = "停止";
                    if (((NodeTag)treeView1.SelectedNode.Tag).isFile)
                    {
                        fileChan.post(tuple.make(srcFile, dstFile));
                    }
                    else
                    {
                        dirChan.post(tuple.make(srcFile, dstFile));
                    }
                }
            }
            else if (button_Load.Text == "停止")
            {
                fileChan.post(default);
                dirChan.post(default);
                dragChan.post(default);
            }
        }

        private void treeView1_DragEnter(object sender, DragEventArgs e)
        {
            if (button_Load.Text != "传输")
            {
                return;
            }
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void treeView1_DragDrop(object sender, DragEventArgs e)
        {
            if (button_Load.Text != "传输")
            {
                return;
            }
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                TreeNode node = treeView1.GetNodeAt(treeView1.PointToClient(new Point { X = e.X, Y = e.Y }));
                if (null != node)
                {
                    NodeTag tag = (NodeTag)node.Tag;
                    if (!tag.isFile)
                    {
                        treeView1.SelectedNode = node;
                        button_Load.Text = "停止";
                        string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
                        dragChan.post(tuple.make(node, files));
                    }
                }
            }
        }

        private void treeView1_DragOver(object sender, DragEventArgs e)
        {
            if (button_Load.Text != "传输")
            {
                return;
            }
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                TreeNode node = treeView1.GetNodeAt(treeView1.PointToClient(new Point { X = e.X, Y = e.Y }));
                if (null != node)
                {
                    NodeTag tag = (NodeTag)node.Tag;
                    if (!tag.isFile)
                    {
                        e.Effect = DragDropEffects.Copy;
                        treeView1.SelectedNode = node;
                        return;
                    }
                }
            }
            e.Effect = DragDropEffects.None;
        }
    }
}
